#include "ota_http.h"
#include <curl/curl.h>
#include "log/log.h"
#include "ota_memchunk.h"
#include "cJSON/cJSON.h"
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <openssl/sha.h>
#include <string.h>

static size_t ota_http_writeToMemchunk(void *ptr, size_t size, size_t nmemb, void *userp)
{
    Memchunk *memchunk = (Memchunk *)userp;
    size_t realsize = size * nmemb;

    if (ota_memchunk_append(memchunk, ptr, realsize) == 0)
    {
        return realsize;
    }

    return 0;
}

static int ota_http_curl(char *url, void *userp, size_t (*callback)(void *, size_t, size_t, void *))
{
    CURL *curl;
    long response_code;
    curl = curl_easy_init();
    if (!curl)
    {
        log_error("curl_easy_init failed");
        return -1;
    }

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, userp);

    int result = curl_easy_perform(curl);
    if (result != CURLE_OK)
    {
        log_error("curl_easy_perform failed with code %d", result);
        curl_easy_cleanup(curl);
        return -1;
    }

    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
    curl_easy_cleanup(curl);

    if (response_code != 200)
    {
        log_error("curl failed with response code %d", response_code);
        return -1;
    }

    return 0;
}

/**
 * @brief 计算fw_file的sha1值，并写入digest数组，digest数组的长度应固定为sha1的hash长度
 *
 * @param fw_file
 * @param digest
 * @return int
 */
static int ota_http_calculateDigest(const char *fw_file, char *digest)
{
    SHA_CTX ctx;
    unsigned char buf[1024];
    unsigned char hash[SHA_DIGEST_LENGTH];
    FILE *fp = fopen(fw_file, "rb");
    if (!fp)
    {
        goto FW_OPEN_FAIL;
    }
    if (SHA1_Init(&ctx) < 0)
    {
        goto SHA_FAIL;
    }
    int n = fread(buf, 1, sizeof(buf), fp);
    while (n > 0)
    {
        if (SHA1_Update(&ctx, buf, n) < 0)
        {
            goto SHA_FAIL;
        }
        n = fread(buf, 1, sizeof(buf), fp);
    }
    if (SHA1_Final(hash, &ctx) < 0)
    {
        goto SHA_FAIL;
    }

    fclose(fp);
    for (size_t i = 0; i < SHA_DIGEST_LENGTH; i++)
    {
        sprintf(digest + i * 2, "%02x", hash[i]);
    }
    digest[SHA_DIGEST_LENGTH * 2] = '\0';
    return 0;
SHA_FAIL:
    fclose(fp);
FW_OPEN_FAIL:
    return -1;
}

Version *ota_http_getVersion()
{
    Version *version = malloc(sizeof(Version));
    if (!version)
    {
        goto VERSION_MALLOC_FAILED;
    }

    Memchunk *memchunk = ota_memchunk_init();
    if (!memchunk)
    {
        goto MEMCHUNK_MALLOC_FAILED;
    }

    if (ota_http_curl(VERSION_URL, memchunk, ota_http_writeToMemchunk) < 0)
    {
        goto CURL_FAIL;
    }

    cJSON *version_obj = cJSON_Parse(memchunk->data);
    if (!version_obj)
    {
        goto CURL_FAIL;
    }

    cJSON *major_obj = cJSON_GetObjectItem(version_obj, "major");
    cJSON *minor_obj = cJSON_GetObjectItem(version_obj, "minor");
    cJSON *patch_obj = cJSON_GetObjectItem(version_obj, "patch");

    if (!major_obj || !minor_obj || !patch_obj)
    {
        goto JSON_PARSE_FAIL;
    }
    version->major = major_obj->valueint;
    version->minor = minor_obj->valueint;
    version->patch = patch_obj->valueint;
    cJSON_Delete(version_obj);
    ota_memchunk_free(memchunk);

    return version;
JSON_PARSE_FAIL:
    cJSON_Delete(version_obj);
CURL_FAIL:
    ota_memchunk_free(memchunk);
MEMCHUNK_MALLOC_FAILED:
    free(version);
VERSION_MALLOC_FAILED:
    return NULL;
}

int ota_http_getFirmware(char *path)
{
    // 下载固件
    int ret;
    FILE *fw = fopen(path, "w");
    if (!fw)
    {
        log_error("open file %s failed", path);
        goto FW_OPEN_FAIL;
    }

    ret = ota_http_curl(FW_URL, fw, fwrite);
    fclose(fw);

    if (ret < 0)
    {
        log_error("download firmware failed");
        goto FW_OPEN_FAIL;
    }
    // 下载固件的hash值
    Memchunk *fw_remote_digest = ota_memchunk_init();
    if (!fw_remote_digest)
    {
        log_error("init memchunk failed");
        goto FW_OPEN_FAIL;
    }

    if (ota_http_curl(FW_DIGEST_URL, fw_remote_digest, ota_http_writeToMemchunk) < 0)
    {
        log_error("download remote digest failed");
        goto REMOTE_DIGTEST_DOWNLOAD_FAIL;
    }
    log_info("remote digest: %s", fw_remote_digest->data);

    // 计算固件的hash值
    char local_digest[SHA_DIGEST_LENGTH * 2 + 1];
    if (ota_http_calculateDigest(path, local_digest) < 0)
    {
        log_error("calculate digest failed");
        goto REMOTE_DIGTEST_DOWNLOAD_FAIL;
    }
    log_info("local digest: %s", local_digest);

    // 验证固件的hash值
    if (strcmp(local_digest, fw_remote_digest->data) != 0)
    {
        log_error("digest not match");
        goto REMOTE_DIGTEST_DOWNLOAD_FAIL;
    }
    ota_memchunk_free(fw_remote_digest);

    return 0;
REMOTE_DIGTEST_DOWNLOAD_FAIL:
    ota_memchunk_free(fw_remote_digest);
FW_OPEN_FAIL:
    // unlink(path);
    return -1;
}
